﻿using System;
using System.Configuration;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Text;
using System.Web;

namespace Nasa8x.Web.UI.Http.Handlers
{

    public class ResourceHandler : IHttpHandler
    {

        private const bool DO_GZIP = true;
        private bool _RELEASE;
        private readonly static TimeSpan CACHE_DURATION = TimeSpan.FromDays(30);

        public void ProcessRequest(HttpContext context)
        {

            HttpRequest request = context.Request;

            // Read setName, contentType and version. All are required. They are
            // used as cache key
            string setName = request["k"] ?? string.Empty;
            string contentType = "text/javascript"; 
            string type = request["t"] ?? string.Empty;
            _RELEASE = !request.IsLocal;
            if (!string.IsNullOrEmpty(type) && type == "css")
            {
                contentType = "text/css";
            }

            string version = request["v"] ?? string.Empty;

            // Decide if browser supports compressed response
            bool isCompressed = DO_GZIP && CanGZip(context.Request);

            // Response is written as UTF8 encoding. If you are using languages like
            // Arabic, you should change this to proper encoding 
            UTF8Encoding encoding = new UTF8Encoding(false);

            // If the set has already been cached, write the response directly from
            // cache. Otherwise generate the response and cache it
            if (!WriteFromCache(context, setName, version, isCompressed, contentType))
            {
                using (MemoryStream memoryStream = new MemoryStream(5000))
                {
                    // Decide regular stream or GZipStream based on whether the response
                    // can be cached or not
                    using (Stream writer = isCompressed ?(Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) :memoryStream)
                    {

                        // Load the files defined in <appSettings> and process each file
                        string setDefinition =
                            ConfigurationManager.AppSettings[setName] ?? "";
                        string[] fileNames = setDefinition.Split(new char[] { ',',';','|' },StringSplitOptions.RemoveEmptyEntries);

                        foreach (string fileName in fileNames)
                        {
                            byte[] fileBytes = GetFileBytes(context, fileName.Trim(), encoding);
                            writer.Write(fileBytes, 0, fileBytes.Length);
                            
                        }

                        writer.Close();
                    }

                    // Cache the combined response so that it can be directly written
                    // in subsequent calls 
                    byte[] responseBytes = memoryStream.ToArray();

                 // string _cacheKey=  GetCacheKey(setName, version, isCompressed);

                 //   CacheWrapper.Add(_cacheKey,responseBytes,);

                    context.Cache.Insert(GetCacheKey(setName, version, isCompressed),
                        responseBytes, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
                        CACHE_DURATION);

                    // Generate the response
                    WriteBytes(responseBytes, context, isCompressed, contentType);
                }
            }
        }

        private static byte[] GetFileBytes(HttpContext context, string virtualPath, Encoding encoding)
        {
            if (virtualPath.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
            {
                using (WebClient client = new WebClient())
                {
                    return client.DownloadData(virtualPath);
                }
            }
            else
            {
                string physicalPath = context.Server.MapPath(virtualPath);
                byte[] bytes = File.ReadAllBytes(physicalPath);
                // TODO: Convert unicode files to specified encoding. For now, assuming
                // files are either ASCII or UTF8
                return bytes;
            }
        }

        private bool WriteFromCache(HttpContext context, string setName, string version,
            bool isCompressed, string contentType)
        {
            if (!_RELEASE) return false;

            byte[] responseBytes = context.Cache[GetCacheKey(setName, version, isCompressed)] as byte[];

            if (null == responseBytes || 0 == responseBytes.Length) return false;

            WriteBytes(responseBytes, context, isCompressed, contentType);
            return true;
        }

        private void WriteBytes(byte[] bytes, HttpContext context,
            bool isCompressed, string contentType)
        {
            HttpResponse response = context.Response;

            response.AppendHeader("Content-Length", bytes.Length.ToString());
            response.ContentType = contentType;
            if (isCompressed)
                response.AppendHeader("Content-Encoding", "gzip");

            context.Response.Cache.SetCacheability(HttpCacheability.Public);
            context.Response.Cache.SetExpires(DateTime.Now.Add(CACHE_DURATION));
            context.Response.Cache.SetMaxAge(CACHE_DURATION);
            context.Response.Cache.AppendCacheExtension("must-revalidate, proxy-revalidate");

            response.OutputStream.Write(bytes, 0, bytes.Length);
            response.Flush();
        }

        private bool CanGZip(HttpRequest request)
        {
            string acceptEncoding = request.Headers["Accept-Encoding"];
            if (!string.IsNullOrEmpty(acceptEncoding) &&
                 (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")))
                return true;
            return false;
        }

        private string GetCacheKey(string setName, string version, bool isCompressed)
        {
            return "ResourceCompress." + setName + "." + version + "." + isCompressed;
        }

        public bool IsReusable
        {
            get
            {
                return true;
            }
        }
    }
}
